package com.jiangyg.mall.core.utils.bean;

import com.jiangyg.mall.core.utils.time.DateUtils;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.converters.DateConverter;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;


/**
 * 类描述：JavaBean常用工具类
 *
 * @author jiangyg
 * @version 2.0
 * @date 2019-10-31
 */
public class BeanUtils {

    private static final Logger logger = LoggerFactory.getLogger(BeanUtils.class);

    static {
        // 重新注册日期字符串转Date转换器
        DateConverter dateConverter = new DateConverter();
        // TODO 格式字符串重新规划
        dateConverter.setPatterns(new String[]{"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.SSS"});
        ConvertUtils.deregister(Date.class);
        ConvertUtils.register(dateConverter, Date.class);
    }

    /**
     * 功能描述： 基于可用的属性getter和setter克隆bean，即使bean类本身没有实现Cloneable。
     *
     * @param bean 要克隆的bean对象
     * @return 克隆的bean
     * @throws Exception 对象克隆异常
     */
    public static Object cloneBean(final Object bean) throws Exception {
        // TODO 看看有没有优化空间
        return BeanUtilsBean.getInstance().cloneBean(bean);
    }

    /**
     * 功能描述：从源bean复制所有相同属性值到目标bean
     *
     * @param dest 目标bean
     * @param orig 源bean
     * @throws Exception 对象复制异常
     */
    public static void copyProperties(final Object dest, final Object orig) throws Exception {
        BeanUtilsBean.getInstance().copyProperties(dest, orig);
    }

    /**
     * 功能描述：从源bean转换为指定class类型的bean
     *
     * @param orig  源bean
     * @param clazz 转换bean的class
     * @throws Exception 结果集转换异常
     */
    public static <T> T transform(final Object orig, Class<T> clazz) throws Exception {
        if (orig == null) {
            return null;
        }
        T dest = clazz.newInstance();
        BeanUtilsBean.getInstance().copyProperties(dest, orig);
        return dest;
    }

    /**
     * 功能描述：从源bean集合转换为指定class类型的集合
     *
     * @param origList 源bean集合
     * @param clazz    转换bean的class
     * @throws Exception 结果集转换异常
     */
    public static <T> List<T> transformList(final List<?> origList, Class<T> clazz) throws Exception {
        if (CollectionUtils.isEmpty(origList)) {
            return Collections.emptyList();
        }
        List<T> destList = new ArrayList<>();
        for (Object orig : origList) {
            T dest = clazz.newInstance();
            BeanUtilsBean.getInstance().copyProperties(dest, orig);
            destList.add(dest);
        }
        return destList;
    }

    /**
     * 功能描述：将指定的属性值复制到目标bean，使用任何类型转换
     *
     * @param bean  目标bean
     * @param name  属性名称
     * @param value 属性值
     * @throws Exception 对象复制异常
     */
    public static void copyProperty(final Object bean, final String name, final Object value) throws Exception {
        BeanUtilsBean.getInstance().copyProperty(bean, name, value);
    }

    /**
     * 功能描述：把bean转化为Map集合
     *
     * @param bean 要转换的bean对象
     * @return Map集合
     */
    public static Map<String, Object> describe(final Object bean) throws Exception {
        return BeanUtilsBean.getInstance().getPropertyUtils().describe(bean);
    }

    /**
     * 功能描述：提取bean中指定属性的值，返回字符串数组
     *
     * @param bean 要提取属性的bean
     * @param name 要提取属性的名称
     * @return 字符串数组
     */
    public static String[] getArrayProperty(final Object bean, final String name) throws Exception {
        return BeanUtilsBean.getInstance().getArrayProperty(bean, name);
    }

    /**
     * 功能描述：提取bean中指定数组属性的值
     *
     * @param bean 要提取属性的bean
     * @param name 要提取属性的名称，数组可以使用name[1]语法，返回指定数组的元素
     * @return 指定数组属性的值
     */
    public static String getIndexedProperty(final Object bean, final String name) throws Exception {
        return BeanUtilsBean.getInstance().getIndexedProperty(bean, name);
    }

    /**
     * 功能描述：提取bean中指定数组属性的值
     *
     * @param bean  要提取属性的bean
     * @param name  要提取属性的名称
     * @param index 数组元素坐标
     * @return 指定数组属性的值
     */
    public static String getIndexedProperty(final Object bean, final String name, final int index) throws Exception {
        return BeanUtilsBean.getInstance().getIndexedProperty(bean, name, index);
    }

    /**
     * 功能描述：提取bean中指定Map属性的值
     *
     * @param bean 要提取属性的bean
     * @param name 要提取属性的名称
     * @return 属性值
     */
    public static String getMappedProperty(final Object bean, final String name) throws Exception {
        return BeanUtilsBean.getInstance().getMappedProperty(bean, name);
    }

    /**
     * 功能描述：提取bean中指定Map属性的值
     *
     * @param bean 要提取属性的bean
     * @param name 要提取属性的名称
     * @param key  要提取Map的key
     * @return 指定Map属性的值
     */
    public static String getMappedProperty(final Object bean, final String name, final String key) throws Exception {
        return BeanUtilsBean.getInstance().getMappedProperty(bean, name, key);
    }

    /**
     * 功能描述：提取bean中指定属性的值，所有数据类型转换为String
     *
     * @param bean 要提取属性的bean
     * @param name 要提取属性的名称
     * @return 属性值转为字符串类型
     */
    public static String getNestedProperty(final Object bean, final String name) throws Exception {
        return BeanUtilsBean.getInstance().getNestedProperty(bean, name);
    }

    /**
     * 功能描述：提取bean中指定属性的值
     *
     * @param bean 要提取属性的bean
     * @param name 要提取属性的名称
     * @return 属性值
     */
    public static Object getProperty(final Object bean, final String name) throws Exception {
        return BeanUtilsBean.getInstance().getPropertyUtils().getProperty(bean, name);
    }

    /**
     * 功能描述：遍历Map集合，如果key在bean中存在，则填充到bean
     *
     * @param bean       要填充的bean
     * @param properties map集合
     */
    public static void populate(final Object bean, final Map<String, ?> properties) throws IllegalAccessException, InvocationTargetException {
        BeanUtilsBean.getInstance().populate(bean, properties);
    }

    /**
     * 功能描述：设置bean的属性值
     *
     * @param bean  要设置的bean
     * @param name  属性名
     * @param value 属性值
     */
    public static void setProperty(final Object bean, final String name, final Object value) throws IllegalAccessException, InvocationTargetException {
        BeanUtilsBean.getInstance().setProperty(bean, name, value);
    }

    /**
     * 功能描述：判断bean是否有指定name属性
     *
     * @param bean 需要判断的bean
     * @param name 属性名称
     * @return 返回Boolean类型，true表示bean有name属性，false反之
     */
    public static boolean hasProperty(final Object bean, final String name) throws Exception {
        return BeanUtilsBean.getInstance().getPropertyUtils().getPropertyDescriptor(bean, name) != null;
    }

    /**
     * 功能描述：判断class是否有指定name属性
     *
     * @param clazz 执行的class
     * @param name  属性名称
     * @return 返回Boolean类型，true表示class有name属性，false反之
     */
    public static boolean hasProperty(Class<?> clazz, final String name) {
        return PropertyUtils.getPropertyDescriptor(clazz, name) != null;
    }

    /**
     * 功能描述：把JavaBean转化为map
     *
     * @param bean JavaBean
     * @return Map
     */
    public static Map<String, Object> beanToMap(Object bean) throws Exception {
        if (bean == null) {
            return null;
        }
        Map<String, Object> map = new HashMap<>();
        // 获取JavaBean的描述器
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
        // 获取属性描述器
        PropertyDescriptor[] props = beanWrapper.getPropertyDescriptors();
        // 对属性迭代
        for (PropertyDescriptor prop : props) {
            // 属性名称
            String propertyName = prop.getName();
            if (StringUtils.equals(propertyName, "class")) {
                continue;
            }
            // 属性值,用getter方法获取
            Method m = prop.getReadMethod();
            // 用对象执行getter方法获得属性值
            Object properValue = m.invoke(bean);
            // 把属性名-属性值 存到Map中
            map.put(propertyName, properValue);
        }
        return map;
    }

    /**
     * 功能描述：把Map转化为JavaBean
     *
     * @param map   集合
     * @param clazz bean类型
     * @return bean
     */
    public static <T> T mapToBean(Map<String, Object> map, Class<T> clazz) throws Exception {
        if (map == null) {
            return null;
        }
        // 创建一个需要转换为的类型的对象
        T obj = clazz.newInstance();
        // 从Map中获取和属性名称一样的值，把值设置给对象(setter方法)
        // 获取JavaBean的描述器
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(obj);
        // 获取属性描述器
        PropertyDescriptor[] props = beanWrapper.getPropertyDescriptors();
        for (PropertyDescriptor prop : props) {
            if (StringUtils.equals(prop.getName(), "class")) {
                continue;
            }
            // 得到属性的setter方法
            Method setter = prop.getWriteMethod();
            // 得到key名字和属性名字相同的value设置给属性
            setter.invoke(obj, adaptive(prop.getPropertyType(), map.get(prop.getName())));
        }
        return obj;
    }

    /**
     * 功能描述：根据类型自动转换值
     *
     * @param type  待转换类型
     * @param value 原值
     * @return 转换后的值
     */
    private static Object adaptive(Class<?> type, Object value) {
        // 如果值为空或者类型相同，则不处理直接返回
        if (value == null || type == value.getClass()) {
            return value;
        }
        // 值从 Integer 类型转为 Long
        if (value.getClass() == Integer.class && type == Long.class) {
            return Long.parseLong(value.toString());
        }
        // 值从 Number 类型转为 String
        if (value.getClass() == Number.class && type == String.class) {
            return value.toString();
        }
        // TODO 更多类型适配
        return value;
    }

    /**
     * 功能描述：Javabean生成字符串
     *
     * @param bean Javabean
     * @return 返回字符串，格式如：JavaBean [property1=value1, property2=value2, ......]
     */
    public static String toString(Object bean) {
        if (bean == null) {
            return null;
        }
        String className = bean.getClass().getSimpleName();
        PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(bean);
        int fieldSize = descriptors.length;
        StringBuilder str = new StringBuilder(className + " [");
        try {
            for (int i = 0; i < fieldSize; i++) {
                PropertyDescriptor descriptor = descriptors[i];
                String propertyName = descriptor.getName();
                if (StringUtils.equals(propertyName, "class")) {
                    continue;
                }
                Method readMethod = descriptor.getReadMethod();
                if (readMethod == null) {
                    continue;
                }
                Object value = readMethod.invoke(bean);
                if (value != null) {
                    // 时间格式化
                    if (value instanceof Date) {
                        value = DateUtils.format(value);
                    }
                }
                str.append(propertyName).append("=").append(value);
                if (i < fieldSize - 1) {
                    str.append(", ");
                }
            }
        } catch (Exception ex) {
            logger.error("Javabean[" + className + "]生成字符串出错，", ex);
        }
        str.append("]");
        return str.toString();
    }

}
